package db

import (
	"GameServer/conf"
	"log"
	"sync"
	"time"

	"github.com/globalsign/mgo"
)

type DBSession struct {
	mgoSession *mgo.Session
	db         string
	rw         *sync.RWMutex
}

func (this *DBSession) Init() {
	this.db = conf.Conf.Game.DbName
	this.mgoSession = this.newSession(conf.Conf.Game.Addrs)
}

//create a mongo session
func (this *DBSession) newSession(url string) *mgo.Session {
	//	mgo.SetDebug(true)
	//	aLogger := log.New(os.Stdout, "mgo:", log.LstdFlags)
	//	mgo.SetLogger(aLogger)
	session, err := mgo.DialWithTimeout(url, 10*time.Second)
	if err != nil {
		panic("dail mongo error " + err.Error())
	}
	//	session.SetPoolLimit(4096 * 2)
	//	session.SetMode(mgo.Monotonic, true)
	session.SetMode(mgo.SecondaryPreferred, true)
	safe := &mgo.Safe{
		WTimeout: 5000,
		J:        true,
	}

	safe.WMode = "majority"

	session.SetSafe(safe)
	session.SetSocketTimeout(600 * time.Second)
	return session
}

//获取mongo连接
func (this *DBSession) GetMongo() *mgo.Session {
	return this.mgoSession.Copy()
}

func (this *DBSession) connect(collection string) (*mgo.Session, *mgo.Collection) {
	ms := this.GetMongo()
	c := ms.DB(this.db).C(collection)
	return ms, c
}

func (this *DBSession) getDb() (*mgo.Session, *mgo.Database) {
	ms := this.GetMongo()
	return ms, ms.DB(this.db)
}

func (this *DBSession) IsEmpty(collection string) bool {
	ms, c := this.connect(collection)
	defer ms.Close()
	count, err := c.Count()
	if err != nil {
		log.Fatal(err)
	}
	return count == 0
}

func (this *DBSession) Count(collection string, query interface{}) (int, error) {
	ms, c := this.connect(collection)
	defer ms.Close()
	return c.Find(query).Count()
}

func (this *DBSession) Insert(collection string, docs ...interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()
	return c.Insert(docs...)
}

func (this *DBSession) FindOne(collection string, query, selector, result interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Select(selector).One(result)
}

func (this *DBSession) FindAll(collection string, query, selector, result interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Select(selector).All(result)
}

func (this *DBSession) FindAllSortLimit(collection string, query, selector, result interface{}, sorts []string, limit int) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Select(selector).Sort(sorts...).Limit(limit).All(result)
}

func (this *DBSession) FindPage(collection string, page, limit int, query, selector, result interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Select(selector).Skip(page * limit).Limit(limit).All(result)
}

func (this *DBSession) FindPageSort(collection string, page, limit int, query, selector interface{}, sorts []string, result interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Select(selector).Sort(sorts...).Skip(page * limit).Limit(limit).All(result)
}

func (this *DBSession) FindPageSort2(collection string, start, limit int, query, selector interface{}, sorts []string, result interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Select(selector).Sort(sorts...).Skip(start).Limit(limit).All(result)
}

func (this *DBSession) FindIter(collection string, query interface{}) *mgo.Iter {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Find(query).Iter()
}

func (this *DBSession) Update(collection string, selector, update interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Update(selector, update)
}

func (this *DBSession) Upsert(collection string, selector, update interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	_, err := c.Upsert(selector, update)
	return err
}

func (this *DBSession) UpdateAll(collection string, selector, update interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	_, err := c.UpdateAll(selector, update)
	return err
}

func (this *DBSession) Remove(collection string, selector interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	return c.Remove(selector)
}

func (this *DBSession) RemoveAll(collection string, selector interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	_, err := c.RemoveAll(selector)
	return err
}

//insert one or multi documents
func (this *DBSession) BulkInsert(collection string, docs ...interface{}) (*mgo.BulkResult, error) {
	ms, c := this.connect(collection)
	defer ms.Close()
	bulk := c.Bulk()
	bulk.Insert(docs...)
	return bulk.Run()
}

func (this *DBSession) BulkRemove(collection string, selector ...interface{}) (*mgo.BulkResult, error) {
	ms, c := this.connect(collection)
	defer ms.Close()

	bulk := c.Bulk()
	bulk.Remove(selector...)
	return bulk.Run()
}

func (this *DBSession) BulkRemoveAll(collection string, selector ...interface{}) (*mgo.BulkResult, error) {
	ms, c := this.connect(collection)
	defer ms.Close()

	bulk := c.Bulk()
	bulk.RemoveAll(selector...)
	return bulk.Run()
}

func (this *DBSession) BulkUpdate(collection string, pairs ...interface{}) (*mgo.BulkResult, error) {
	ms, c := this.connect(collection)
	defer ms.Close()

	bulk := c.Bulk()
	bulk.Update(pairs...)
	return bulk.Run()
}

func (this *DBSession) BulkUpdateAll(collection string, pairs ...interface{}) (*mgo.BulkResult, error) {
	ms, c := this.connect(collection)
	defer ms.Close()

	bulk := c.Bulk()
	bulk.UpdateAll(pairs...)
	return bulk.Run()
}

func (this *DBSession) BulkUpsert(collection string, pairs ...interface{}) (*mgo.BulkResult, error) {
	ms, c := this.connect(collection)
	defer ms.Close()

	bulk := c.Bulk()
	bulk.Upsert(pairs...)
	return bulk.Run()
}

func (this *DBSession) PipeAll(collection string, pipeline, result interface{}, allowDiskUse bool) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	var pipe *mgo.Pipe
	if allowDiskUse {
		pipe = c.Pipe(pipeline).AllowDiskUse()
	} else {
		pipe = c.Pipe(pipeline)
	}
	return pipe.All(result)
}

func (this *DBSession) PipeOne(collection string, pipeline, result interface{}, allowDiskUse bool) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	var pipe *mgo.Pipe
	if allowDiskUse {
		pipe = c.Pipe(pipeline).AllowDiskUse()
	} else {
		pipe = c.Pipe(pipeline)
	}
	return pipe.One(result)
}

func (this *DBSession) PipeIter(collection string, pipeline interface{}, allowDiskUse bool) *mgo.Iter {
	ms, c := this.connect(collection)
	defer ms.Close()

	var pipe *mgo.Pipe
	if allowDiskUse {
		pipe = c.Pipe(pipeline).AllowDiskUse()
	} else {
		pipe = c.Pipe(pipeline)
	}

	return pipe.Iter()

}

func (this *DBSession) Explain(collection string, pipeline, result interface{}) error {
	ms, c := this.connect(collection)
	defer ms.Close()

	pipe := c.Pipe(pipeline)
	return pipe.Explain(result)
}
func (this *DBSession) GridFSCreate(prefix, name string) (*mgo.GridFile, error) {
	ms, d := this.getDb()
	defer ms.Close()

	gridFs := d.GridFS(prefix)
	return gridFs.Create(name)
}

func (this *DBSession) GridFSFindOne(prefix string, query, result interface{}) error {
	ms, d := this.getDb()
	defer ms.Close()

	gridFs := d.GridFS(prefix)
	return gridFs.Find(query).One(result)
}

func (this *DBSession) GridFSFindAll(prefix string, query, result interface{}) error {
	ms, d := this.getDb()
	defer ms.Close()

	gridFs := d.GridFS(prefix)
	return gridFs.Find(query).All(result)
}

func (this *DBSession) GridFSOpen(prefix, name string) (*mgo.GridFile, error) {
	ms, d := this.getDb()
	defer ms.Close()

	gridFs := d.GridFS(prefix)
	return gridFs.Open(name)
}

func (this *DBSession) GridFSRemove(prefix, name string) error {
	ms, d := this.getDb()
	defer ms.Close()

	gridFs := d.GridFS(prefix)
	return gridFs.Remove(name)
}
